void function() {
function lazyLoad(src) {
    var obj = {
        time: Date.now(),
        src: src,
        loader: null,
        canceled: false,
    };
    
    var promise = new Promise((imgLoadDone, imgLoadFail) => {
        obj.load = function() {
            if (obj.loader) {
                return;
            }
            obj.loader = new Promise((resolve, reject) => {
                var img = new Image();
                img.src = obj.src;
                console.log('load start', obj.src, Date.now());
                img.onload = img.onerror = function() {
                    lazyLoad.size++;
                    lazyLoad.fresh();
                    resolve(obj.src);
                    console.log('load end', obj.src, Date.now());
                    setTimeout(resolve, lazyLoad.maxWaitTime, obj.src);
                }
            }).then(imgLoadDone);
                
        };
        lazyLoad.queue.push(obj);
        lazyLoad.fresh();

        
    });

    promise.cancel = function() {
        obj.canceled = true;
    }
    
    return promise;
}

lazyLoad.fresh = function() {
    while (lazyLoad.size > 0 && lazyLoad.queue.length) {
        lazyLoad.size--;
        var cur = lazyLoad.queue.shift();
        if (cur.canceled) {
            console.warn('cancel load', cur.src);
            lazyLoad.size++;
            continue;
        }
        if (cur.src in lazyLoad.loaded) {
            cur.load();
        } else {
            lazyLoad.loaded[cur.src] = true;
            setTimeout(cur.load, lazyLoad.minWaitTime);
        }
    }
}

lazyLoad.size = 3;
lazyLoad.queue = [];
lazyLoad.loaded = {};
lazyLoad.waitTimeForShowLoading = 300;
lazyLoad.minWaitTime = 100;
lazyLoad.maxWaitTime = 1000;

Vue.component('lazy-img', {
    props: {
        src: String,
        width: {
            type: Number,
            default: 0
        },
        height: {
            type: Number,
            default: 0
        },
        img: {
            type: String,
            default: 'b-img'
        },
        useSlot: {
            type: Boolean,
            default: false
        },
        loadingSrc: {
            type: String,
            default: 'loading.svg'
        }
    },
    computed: {
        loadingProp: function() {
            return {
                src: this.curSrc,
                width: this.width,
                height: this.height
            }
        }
    },
    data: function() {
        return {
            curSrc: '',
            state: 'loading'
        }
    },
    created: function() {
        var vm = this;
        vm.loading = lazyLoad(vm.src);
        var showLoadingImage = setTimeout(() => {
            vm.curSrc = vm.loadingSrc;
        }, lazyLoad.waitTimeForShowLoading + lazyLoad.minWaitTime);
        vm.loading.then(() => {
            clearTimeout(showLoadingImage);
            vm.curSrc = vm.src;
            vm.state = '';
        });
    },
    destroyed: function() {
        this.loading.cancel();
    },
    template: `
        <div class="lazy-img" :class="[state]">
            <slot v-if="useSlot" v-bind="loadingProp" class='xxx'></slot>
            <component v-else :is="img" v-bind="loadingProp" :key="curSrc"></component>
        </div>
    `
});

}();

void function() {
var baseImg = {
    props: {
        src: String,
        width: {
            type: Number,
            default: 0
        },
        height: {
            type: Number,
            default: 0
        },
        img: {
            type: String,
            default: 'b-img'
        },
        renderTime: {
            type: Number,
            default: 0
        }
    },
    template: `
        <div
            :class="[name, {loaded}, 'resize-img']"
            :style="style">
            <component
                :key="src"
                :is="img"
                :src="src"
                :style="imgStyle"
                ref="img"
                @load-fail="loadFail"
                @load-success="loadSuccess"></component>
        </div>
        `,
    data: function() {
        return {
            loaded: false,
            imgSize: [0, 0]
        }
    },
    computed: {
        imgWidth: function() {
            return this.imgSize[0];
        },
        imgHeight: function() {
            return this.imgSize[1];
        },
        centerX: function() {
            return true;
        },
        centerY: function() {
            return true;
        },
        style: function() {
            return {
                width: this.width + 'px', height: this.height + 'px'
            }
        },
        imgStyle: function() {
            var size = [this.imgWidth, this.imgHeight],
                arr = [this.width, this.height],
                centerState = [this.centerX, this.centerY];
            var offset = size.map((x, i) => centerState[i] ? (arr[i] - x) / 2 + 'px' : 0);
            return {
                
                width: size[0] + 'px',
                height: size[1] + 'px',
                transform: `translateZ(0) translate(${offset.join(',')})`
            }
        }
    },
    activated: function(vm) {
        this.$emit('img-resize', this.imgWidth, this.imgHeight);
    },
    methods: {
        loadFail: function(e) {
            this.$el.title = '加载失败';
            this.resize(e.target);
        },
        loadSuccess: function(e) {
            this.resize(e.target);
            this.$emit('load', e);
        },
        resize: function() {
            var img = this.$refs.img.$el;
            if (!img) {
                return;
            }
            var ns = this.getSize(img.naturalWidth, img.naturalHeight),
                bl = img.naturalWidth / img.naturalHeight;
            if (ns[0] < 0 && ns[1] < 0) {
                // 高度随视窗以自定义比例缩放
                bl = ns[0] / ns[1];
                ns[0] = this.width;
                ns[1] = ns[0] / bl;
            } else if (ns[0] > 0 && ns[1] < 0) {
                // 高度等比例缩放
                ns[1] = ns[0] / bl;
            } else if (ns[0] < 0 && ns[1] > 0) {
                // 宽度等比例缩放
                ns[0] = ns[1] * bl;
            }
            this.imgSize = ns;
            this.$emit('img-resize', this.imgWidth, this.imgHeight);
        }
    },
    created: function() {
        var vm = this;
        vm.$nextTick(() => {
            vm.$watch('width', () => {
                vm.resize(vm.$refs.img.$el);
            });
            vm.$watch('height', () => {
                vm.resize(vm.$refs.img.$el);
            });
            vm.$watch('renderTime', () => {
                vm.resize(vm.$refs.img.$el);
            })
            vm.$watch('src', () => {
                vm.loaded = false;
            });
            vm.$on('load', function() {
                vm.loaded = true;
            });
        })
            
    }
}

Vue.component('self-wh-img', {
    props: ['size'],
    data: function() {
        return {
            renderTime: 0
        }
    },
    inheritAttrs: false,
    methods: {
        sizeFn: function() {
            var vm = this;
            return function(o_w, o_h) {
                return vm.size;
            }
        },
        getListeners: function() {
            var obj = Object.assign({}, this.$listeners);
            delete obj['img-resize'];
        }
    },
    template: `
        <component v-on="$listeners" is="scroll-img" v-bind="$attrs" :renderTime="renderTime" :getSize="sizeFn()" b-name="self-wh-img"></component>
    `,
    created: function() {
        var vm = this;
        vm.$watch('size', function() {
            vm.renderTime++;
        })
    }
})

Vue.component('contain-img', {
    extends: baseImg,
    data: function() {
        return {
            name: 'contain-img'
        }
    },
    methods: {
        getSize: function(o_w, o_h) {
            var scaled = Math.min(this.width / o_w, this.height / o_h);
            var n_w = o_w * scaled,
                n_h = o_h * scaled;
            return [n_w, n_h];
        }
    }
})


Vue.component('cover-img', {
    extends: baseImg,
    data: function() {
        return {
            name: 'cover-img'
        }
    },
    methods: {
        getSize: function(o_w, o_h) {
            var scaled = Math.max(this.width / o_w, this.height / o_h);
            var n_w = o_w * scaled,
                n_h = o_h * scaled;
            return [n_w, n_h];
        }
    }
});


Vue.component('follow-width-img', {
    props: [],
    inheritAttrs: false,
    methods: {
        sizeFn: function() {
            return function(o_w, o_h) {
                var scaled = this.width / o_w;
                var n_w = o_w * scaled, n_h = o_h * scaled;
                return [n_w, n_h];
            }
        }
    },
    template: `
        <component v-on="$listeners" is="scroll-img" v-bind="$attrs" :getSize="sizeFn()" b-name="follow-width-img"></component>
    `
})

Vue.component('follow-height-img', {
    props: [],
    inheritAttrs: false,
    methods: {
        sizeFn: function() {
            return function(o_w, o_h) {
                var scaled = this.height / o_h;
                var n_w = o_w * scaled, n_h = o_h * scaled;
                return [n_w, n_h];
            }
        }
    },
    template: `
        <component v-on="$listeners" is="scroll-img" v-bind="$attrs" :getSize="sizeFn()" b-name="follow-height-img"></component>
    `
})


Vue.component('scroll-img', {
    props: {
        'width': Number, 'height': Number, 'bName': {type: String, default: "scroll-img"}
    },
    inheritAttrs: false,
    methods: {

    },
    template: `
        <scroll-view :view-width="width" :view-height="height" dragable ref="view">
            <component
                class="content"
                is="scroll-img-base"
                :width="width"
                :height="height"
                v-bind="$attrs"
                :b-name="bName"
                slot-scope="{scroll}" :style="scroll"
                v-on="$listeners"
            ></component>
        </scroll-view>
        
    `
})

Vue.component('scroll-img-base', {
    extends: baseImg,
    data: function() {
        return {
            name: this.bName
        }
    },
    props: {
        bName: {
            type: String,
            default: 'scroll-img-base'
        },
        getSize: {
            type: Function,
            default: function(o_w, o_h) {
                return [o_w, o_h];
            }
        }
    },
    computed: {
        centerX: function() {
            return this.imgWidth < this.width;
        },
        centerY: function() {
            return this.imgHeight < this.height;
        },
        totalWidth: function() {
            return this.imgWidth;
        },
        totalHeight: function() {
            return this.imgHeight;
        },
        style: function() {
            return {
                width: Math.max(this.width, this.totalWidth) + 'px',
                height: Math.max(this.height, this.totalHeight) + 'px'
            }
        }
    }
});

}();


Vue.component('b-img', {
    props: {
        src: {
            type: String,
            default: 'loading.svg'
        },
        width: Number,
        height: Number,
        errorImg: {
            type: String,
            default: 'error.png'
        },
        alt: {
            type: String,
            default: ''
        }
    },
    data: function() {
        return {
            failed: false,
            stateChange: false,
        }
    },
    template: `<img v-if="src" :style="{opacity: stateChange ? '' : 0}" :class="{'load-fail': failed}" :alt="altMsg" :src="curSrc" @load="loaded" @error="loadError"/>`,
    computed: {
        curSrc: function() {
            return this.failed ? this.errorImg : this.src;
        },
        altMsg: function() {
            return this.alt ? this.alt : `加载图片${this.src}失败`
        }
    },
    methods: {
        loaded: function(e) {
            this.$emit('load', e);
            if (!this.failed) {
                this.$emit('load-success', e);
            } else {
                this.$emit('load-fail', e);
            }
            this.stateChange = true;
        },
        loadError: function() {
            if (this.failed) {
                throw `cannot load errorImg: ${this.errorImg}`;
            }
            this.failed = true;
            this.stateChange = true;
        }
    }
});